﻿//-----------------------------------------------------------------------
// <copyright file="SmartManipulator.Translate.cs" company="Nathan Miller">
// Copyright (c) Nathan Miller.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
//-----------------------------------------------------------------------
namespace TileTool.Base.Manipulator.Smart
{
    using TileTool.Common.Math;
    using TileTool.Editor.Grid;
    using TileTool.Editor.Tool.Scene;
    using TileTool.Editor.Transform;
    using TileTool.Render;
    using TileTool.Scene.Collision;
    using TileTool.Scene.Entity;

    /// <summary>
    /// Translate implementation of the smart manipulator.
    /// </summary>
    public partial class SmartManipulator
    {
        #region Methods - Public

        /// <summary>
        /// Get the current translate amount.
        /// </summary>
        /// <returns>Returns the current translate amount.</returns>
        public Vector2D Translate_GetAmount()
        {
            ITransformState state = this.Owner.State;
            ISnapState snap = state.SnapState;
            Vector2D dragAmount = this.ComputeDragAmount();

            // If grid snap is not enabled, just return whatever our
            // calculated drag amount is.
            if (!snap.SnapToGrid)
            {
                return dragAmount;
            }

            // If we are snapping, we want the WS position of where we started to drag.
            //
            // This will either be the position of the manipulator's matrix or the
            // NSEW class handle that we started dragging.
            Vector2D handleDragStartWS;
            
            if (DragHandle.Id == (int)SmartManipulatorHandlePosition.Translate)
            {
                handleDragStartWS = this.DragStartMatrix.Pos;
            }
            else
            {
                handleDragStartWS = this.DragStartMatrix * DragHandle.DragStartPos;
            }
            
            // Compute an initial offset which snaps us to the grid at the start of the drag.
            IGrid grid = state.Grid;
            Vector2D snappedStartPosWS = grid.SnapVector(handleDragStartWS);
            Vector2D startSnapOffset = snappedStartPosWS - handleDragStartWS;

            // Now snap the drag amount to the grid so we only move in grid snap size
            // increments.
            dragAmount = grid.SnapVector(dragAmount);

            return dragAmount + startSnapOffset;
        }

        #endregion

        #region Methods - Private

        /// <summary>
        /// Setup translate handles.
        /// </summary>
        private void Translate_SetupHandles()
        {
            SmartManipulatorTranslateHandle handle = new SmartManipulatorTranslateHandle(this.Translate_HandleCollision);

            handle.Id = (int)SmartManipulatorHandlePosition.Translate;

            this.AddManipulatorHandle(handle);
        }

        /// <summary>
        /// Called when the manipulator is reset.
        /// </summary>
        private void Translate_Reset()
        {
        }

        /// <summary>
        /// Update the handle positions.
        /// </summary>
        private void Translate_UpdateHandlePositions()
        {
            Size2D halfSize = this.Size.Half;

            // To be consistent, always position the handle in the center of our bounds.
            this.PositionHandle(SmartManipulatorHandlePosition.Translate, new Vector2D(halfSize.Width, halfSize.height));
        }

        /// <summary>
        /// Called when a drag begins.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="canvasPos">Canvas position position where the drag started.</param>
        private void Translate_DragBegin(SceneToolInputData sceneInputData, Vector2D canvasPos)
        {
        }

        /// <summary>
        /// Called when a drag ends.
        /// </summary>
        private void Translate_DragEnd()
        {
        }

        /// <summary>
        /// Called when a drag update happens on any handle.
        /// </summary>
        /// <param name="sceneInputData">Scene input data.</param>
        /// <param name="canvasPos">Canvas position position where the mouse is currently.</param>
        private void Translate_DragUpdate(SceneToolInputData sceneInputData, Vector2D canvasPos)
        {
        }

        /// <summary>
        /// Function which takes care of handle collision.
        /// </summary>
        /// <param name="posHS">Test position in handle space.</param>
        /// <returns>Returns true if there was a collision.</returns>
        private bool Translate_HandleCollision(Vector2D posHS)
        {
            ManipulatorHandle handle = this.GetHandle(SmartManipulatorHandlePosition.Translate);
            Vector2D posManipulatorSpace = posHS + handle.Pos;

            IEntity entity = this.Translate_ProcessEntityCollision(posManipulatorSpace);
            if (entity != null)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Process collision against other handles that we can use as a drag start point.
        /// </summary>
        /// <param name="posManipulatorSpace">Posiiton to test in manipulator space.</param>
        /// <returns>Return the position of the handle we hit or None if we did not hit one.</returns>
        private SmartManipulatorHandlePosition Translate_ProcessHandleCollision(Vector2D posManipulatorSpace)
        {
            foreach (ManipulatorHandle handle in Handles)
            {
                switch ((SmartManipulatorHandlePosition)handle.Id)
                {
                    case SmartManipulatorHandlePosition.North:
                    case SmartManipulatorHandlePosition.South:
                    case SmartManipulatorHandlePosition.East:
                    case SmartManipulatorHandlePosition.West:
                    case SmartManipulatorHandlePosition.NorthWest:
                    case SmartManipulatorHandlePosition.NorthEast:
                    case SmartManipulatorHandlePosition.SouthWest:
                    case SmartManipulatorHandlePosition.SouthEast:
                        break;
                    default:
                        continue;
                }

                Vector2D posHandleSpace = posManipulatorSpace - handle.Pos;
                if (handle.TestPoint(posHandleSpace))
                {
                    return (SmartManipulatorHandlePosition)handle.Id;
                }
            }

            return SmartManipulatorHandlePosition.None;
        }

        /// <summary>
        /// Process collision against entities to see if we are over any.
        /// </summary>
        /// <param name="posManipulatorSpace">Posiiton to test in manipulator space.</param>
        /// <returns>Return the entity we hit.</returns>
        private IEntity Translate_ProcessEntityCollision(Vector2D posManipulatorSpace)
        {
            Vector2D posWS = this.Matrix * posManipulatorSpace;
            ManipulatorToolBase owner = this.Owner;
            IEntityCollection selection = owner.Scene.Selection;

            PointCollisionRequest req = new PointCollisionRequest();

            req.Point = posWS;
            req.Camera = null;

            foreach (IEntity entity in selection)
            {
                IEntity hitEntity = entity.HandleCollision(req);
                if (hitEntity != null)
                {
                    return hitEntity;
                }
            }

            return null;
        }

        /// <summary>
        /// Compute the current drag amount that takes into account any
        /// movement limitation.
        /// </summary>
        /// <returns>Returns the current drag amount.</returns>
        private Vector2D ComputeDragAmount()
        {
            Vector2D dragAmount = DragAmountWorld;
            ITransformState state = this.Owner.State;

            if (this.translateXOnly && this.translateYOnly)
            {
                return dragAmount;
            }

            if (this.translateXOnly)
            {
                dragAmount = state.Matrix.X * state.Matrix.X.Dot(dragAmount);
            }
            else if (this.translateYOnly)
            {
                dragAmount = state.Matrix.Y * state.Matrix.Y.Dot(dragAmount);
            }

            ISnapState snapState = state.SnapState;

            if (snapState.TranslateSnap)
            {
                dragAmount = MathUtility.SnapVector(dragAmount, snapState.TranslateSnapAmount);
            }

            return dragAmount;
        }

        #endregion
    }
}